package cn.spikego.advicelite.client

import cn.spikego.advicelite.client.core.command.CommandManager
import cn.spikego.advicelite.client.core.event.EventDistributor
import cn.spikego.advicelite.client.core.event.EventInjector
import cn.spikego.advicelite.client.core.gui.GuiIngameRenderer
import cn.spikego.advicelite.client.core.module.ModuleManager
import cn.spikego.advicelite.client.core.utils.minecraft.MinecraftUtil
import cn.spikego.advicelite.client.eventapi.EventManager
import cn.spikego.advicelite.client.mapping.MappingManager
import cn.spikego.advicelite.client.mapping.VersionMappingsLoader
import cn.spikego.advicelite.client.mapping.loader.MappingsLoader
import cn.spikego.advicelite.client.tasks.TickingTask
import cn.spikego.advicelite.client.tranformers.TestTransformer
import cn.spikego.advicelite.client.version.VersionFinder
import cn.spikego.advicelite.common.LogUtil
import java.lang.instrument.Instrumentation
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import java.util.logging.Level
import java.util.logging.Logger

class AdviceLite {
    private var initialized = false
    private val tickingService: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor()
    private val mappingManager = MappingManager()
    private val eventDistributor = EventDistributor()
    private val commandManager = CommandManager()
    private val moduleManager = ModuleManager()
    private var minecraftUtil: MinecraftUtil? = null

    companion object {
        const val DEBUG = true
        @JvmStatic
        var instrumentation: Instrumentation? = null
        private var instance: AdviceLite? = null

        @JvmStatic
        fun getInstance(): AdviceLite = instance!!

                @JvmStatic
                        fun getMinecraftUtil(): MinecraftUtil? = getInstance().minecraftUtil

        @JvmStatic
        fun getMappingManager(): MappingManager = getInstance().mappingManager

        @JvmStatic
        fun invokeClient(inst: Instrumentation) {
            try {
                instrumentation = inst
                val instObj = instance ?: AdviceLite().also { instance = it }
                instObj.initialize()
                instObj.start()
            } catch (e: Exception) {
                LogUtil.err("Failed to start client!")
                e.printStackTrace()
            }
        }
    }

    init {
        instance = this
    }

    @Throws(Exception::class)
    fun initialize() {
        Logger.getLogger("org.reflections").level = Level.OFF

        LogUtil.log("Initializing client...")
        tickingService.scheduleAtFixedRate(TickingTask(), 0, 50, TimeUnit.MILLISECONDS)

        LogUtil.log("Checking client version...")
        val version = VersionFinder().findVersion()
        if (version == null) {
            LogUtil.log("Failed to find client version!")
            return
        }

        LogUtil.log("Client version: %s", version.version)
        LogUtil.log("Client protocol: %s", version.protocol)

        LogUtil.log("Downloading mappings...")
        val mappingsLoader = MappingsLoader(mappingManager)
        val mappingsUrl = VersionMappingsLoader().loadFromVersion(version)
        mappingsLoader.loadFromUrl(mappingsUrl)

        if (mappingManager.mappedClasses.isEmpty()) {
            LogUtil.err("Failed to load mappings data!")
            return
        }

        LogUtil.log("Loaded %s mapped classes!", mappingManager.mappedClasses.size)

        LogUtil.log("Injecting events...")
        val eventInjector = EventInjector()
        eventInjector.initialize()

        commandManager.initialize()
        moduleManager.initialize()

        minecraftUtil = MinecraftUtil.getFromVersion(version.version)
        if (minecraftUtil == null) {
            LogUtil.err("Unsupported version: %s", version.version)
            return
        }

        LogUtil.log("Client initialized!")

        instrumentation?.addTransformer(TestTransformer())
        val clazz = getMappingManager().translateClassName("net.minecraft.world.entity.player.Player")
        instrumentation?.retransformClasses(Class.forName(clazz))

        initialized = true
    }

    fun start() {
        if (!initialized) {
            LogUtil.err("Failed to start: client is not initialized!")
            return
        }
        EventManager.register(GuiIngameRenderer())
        LogUtil.log("Starting client...")
    }

    fun getEventDistributor(): EventDistributor = eventDistributor
    fun getModuleManager(): ModuleManager = moduleManager
    fun getCommandManager(): CommandManager = commandManager
}